﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Util;
using JetBrains.ReSharper.LiveTemplates;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Naming.Extentions;
using JetBrains.ReSharper.Psi.Naming.Impl;
using JetBrains.ReSharper.Psi.Naming.Settings;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
using MugenMvvmToolkit.ReSharper.Infrastructure;
using MugenMvvmToolkit.ReSharper.PluginModels;

namespace MugenMvvmToolkit.ReSharper.ViewModels.ContextActions
{
    [ContextAction(Name = "WrapViewModelToAction", Group = "C#", Priority = 1)]
    public class WrapViewModelToAction : ContextActionImplBase
    {
        #region Fields

        private ILocalVariableDeclaration _localVariable;

        #endregion

        #region Constructors

        public WrapViewModelToAction([NotNull] ICSharpContextActionDataProvider provider)
            : base(provider)
        {
        }

        #endregion

        #region Overrides of ContextActionImplBase<ICSharpContextActionDataProvider>

        /// <summary>
        ///     Check if this action is available at the constructed context.
        ///     Actions could store precalculated info in <paramref name="cache" /> to share it between different actions
        /// </summary>
        /// <returns>
        ///     true if this bulb action is available, false otherwise.
        /// </returns>
        public override bool IsAvailable(IUserDataHolder cache)
        {
            if (Provider.SelectedElement == null)
                return false;
            _localVariable = Provider.SelectedElement as ILocalVariableDeclaration ??
                             Provider.SelectedElement.Parent as ILocalVariableDeclaration;
            if (_localVariable == null || !_localVariable.DeclaredElement.Type.IsOfType(TypeProvider.ViewModelInterface))
                return false;

            IEnumerable<IBulbAction> items = TypeProvider
                .GetWrapperTypes()
                .Where(model => model.Type.IsResolved && model.IsSupported(_localVariable.DeclaredElement.Type))
                .OrderByDescending(model => model.Priority)
                .SelectMany(ToActions);
            Actions = new List<IBulbAction>(items);
            return Actions.Count != 0;
        }

        #endregion

        #region Methods

        private IEnumerable<IBulbAction> ToActions(WrapperMetaModel model)
        {
            if (model.IsDisposable)
                yield return new DelegateBulbAction((solution, indicator) => Execute(solution, model.Type, true),
                    string.Format(UiResources.WrapToWithDisposeActionFormat, model.Name));
            yield return
                new DelegateBulbAction((solution, indicator) => Execute(solution, model.Type, false),
                    string.Format(UiResources.WrapToActionFormat, model.Name));
        }

        private Action<ITextControl> Execute(ISolution solution, IDeclaredType type, bool isDisposable)
        {
            if (_localVariable.Parent == null)
                return null;
            bool generic;
            type = Extensions.UpdateType(type, TypeProvider.Object, out generic);

            var declarationStatement = _localVariable.Parent.Parent as IDeclarationStatement;
            ITreeNode addedNode = null;
            if (declarationStatement != null)
            {
                ITreeNode statement = CreateWrapStatement(type, isDisposable, null);
                Extensions.AddTypeNamespace(Provider.ElementFactory, TypeProvider.ViewModelExtensions,
                    declarationStatement);
                addedNode = ModificationUtil.AddChildAfter(declarationStatement, statement);
            }
            var usingStatement = _localVariable.Parent.Parent as IUsingStatement;
            if (usingStatement != null)
            {
                ITreeNode statement = CreateWrapStatement(type, isDisposable, usingStatement);
                Extensions.AddTypeNamespace(Provider.ElementFactory, TypeProvider.ViewModelExtensions, usingStatement);
                addedNode = AddChildToUsingStatement(usingStatement, statement, isDisposable);
                ContextActionUtils.FormatWithDefaultProfile(usingStatement);
            }
            if (addedNode == null)
                return null;
            return control =>
            {
                var expression = addedNode.FindChild<ILocalVariableDeclaration>();
                if (expression == null)
                    return;
                //                var hotspotInfos = new List<HotspotInfo>();
                DocumentRange documentRange = expression.NameIdentifier.GetDocumentRange();
                var field = new TemplateField("NAME", new TextHotspotExpression(GetNames(type, expression)), 0);
                //                hotspotInfos.Add();
                /*var typeUsage = addedNode.FindChild<ITypeArgumentList>();
                if (typeUsage != null)
                    hotspotInfos.AddRange(InheritFromViewModelAction.GetGenericTypesHotspots(typeUsage.TypeArgumentNodes,
                        Provider.PsiModule,
                        Provider.PsiFile.Language));*/
                LiveTemplatesManager.TryExecuteHotspotDefault(solution, control, new HotspotInfo(field, documentRange));
            };
        }

        private static ITreeNode AddChildToUsingStatement(IUsingStatement usingStatement,
            ITreeNode wrapperNode, bool isDisposable)
        {
            if (usingStatement.Body == null)
                return ModificationUtil.AddChildAfter(usingStatement, wrapperNode);
            if (isDisposable)
                return ModificationUtil.AddChildBefore(usingStatement.Body, wrapperNode);
            var block = usingStatement.Body as IBlock;
            if (block != null)
            {
                var st = (ICSharpStatement)wrapperNode;
                return block.AddStatementAfter(st, null);
            }
            return ModificationUtil.AddChild(usingStatement.Body, wrapperNode);
        }

        private ITreeNode CreateWrapStatement(IType type, bool isDisposable, IUsingStatement usingStatement)
        {
            string baseExp = string.Format("var wrapper = {0}.WrapTo<$0>()", _localVariable.DeclaredName);
            if (isDisposable)
            {
                baseExp = string.Format("using({0})", baseExp);
                if (usingStatement == null || usingStatement.Body == null)
                    baseExp += "{ }";
            }
            else
                baseExp += ";";
            return Provider
                .ElementFactory
                .CreateStatement(baseExp, type, _localVariable);
        }

        private IList<string> GetNames(IDeclaredType type, ILocalVariableDeclaration localVariable)
        {
            var psiServices = TypeProvider.PsiModule.GetPsiServices();
            var collection = psiServices.Naming.Suggestion.CreateEmptyCollection(PluralityKinds.Plural, localVariable.Language, true, localVariable);
            var entryOptions = new EntryOptions
            {
                PluralityKind = PluralityKinds.Plural,
                SubrootPolicy = SubrootPolicy.Decompose,
                Emphasis = Emphasis.Good
            };
            collection.Add(localVariable.DeclaredElement, entryOptions);
            collection.Add(type, entryOptions);
            collection.Prepare(NamedElementKinds.Locals, ScopeKind.LocalSelfScoped, new SuggestionOptions { UniqueNameContext = localVariable });
            var names = collection.AllNames();
            return names;
        }

        #endregion
    }
}